/*
Copyright (C) 1996-1997 GX Media, Inc.
Copyright (C) 2010 Ronie Salgado

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "stdafx.h"
#include "QooleDoc.h"
#include "QMainFrame.h"
#include "QChildFrame.h"
#include "QTreeView.h"
#include "QEntSel.h"
#include "QooleView.h"
#include "QDraw.h"
#include "ProgressWindow.h"
#include "Selector.h"
#include "MapIO.h"

// QDocHint.
QDocHint::QDocHint()
{
	flags = 0;
	scope = NULL;
}

QDocHint::~QDocHint()
{
}

// QooleDoc.
IMPLEMENT_DYNAMIC_CLASS(QooleDoc, wxDocument)

QooleDoc::QooleDoc()
{
	// Init Document.
	rootObject = NULL;
	holdBrush = NULL;
	holdManipObj = NULL;
	selector = NULL;
	childFrame = NULL;

	game = NULL;
	entList = NULL;
}

QooleDoc::~QooleDoc()
{
	SpecialViews::iterator it = specialViews.begin();
	while(it != specialViews.end())
		(*it++)->RegisterDocument(NULL);

	delete selector;
}

wxString QooleDoc::GetDocName() const
{
	wxFileName docName = GetFilename();
	return docName.GetName();
}

bool QooleDoc::DeleteContents()
{
	// Sanity.
	ASSERT(holdBrush == NULL);

	if (rootObject != NULL) {
		delete rootObject;
		rootObject = NULL;
	}

	return wxDocument::DeleteContents();
}

bool QooleDoc::OnCreate(const wxString& path, long flags)
{
	// Don't create the initial view now.
	return true;
}

bool QooleDoc::OnNewDocument()
{
	if(!wxDocument::OnNewDocument())
		return false;

	// Show the Game/Entity set dialog.
	QEntSel entSel(GetMainFrame());
	if(entSel.ShowModal() != wxID_OK)
		return false;

	if(entSel.gameName.empty())
		return false;

	game = Game::Find(entSel.gameName);
	palName = entSel.palName;
	if(!Game::Set(game, palName, QDraw::textureGamma))
		return false;

	// Use the game grid and snap default size.
	gridSnapSize = game->GetDefaultGridSize();
	rotSnapSize = game->GetDefaultRotSnapSize();

	// Use by default grid and rotation snap.
	gridSnap = rotSnap = true;

	entList = EntList::Find(entSel.entName);
	EntList::Set(entList);

	ASSERT(rootObject == NULL);
	rootObject = new Object;

	// Add world spawn entity.
	Entity *worldSpawn = new Entity(wxT("worldspawn"));
	rootObject->SetEntity(worldSpawn);

	// Notify the frames.
	GetMainFrame()->GetTreeView()->AddDocument(this);
	GetMainFrame()->SetDeskTopDocument(this);

	Modify(true);
	return true;
}

bool QooleDoc::OnOpenDocument(const wxString& filename)
{
	// Check the extension.
	wxFileName pname = filename;
	wxString ext = pname.GetExt();
	if(ext.CmpNoCase(wxT("map")) == 0 ||
	   ext.CmpNoCase(wxT("qle")) == 0)
	{
		return LoadClassicDocument(filename);
	}
	else if(ext.CmpNoCase(wxT("xql")) == 0)
	{
		return LoadXmlDocument(filename);
	}
	else
	{
		wxMessageBox(wxT("Unsupported file format " + ext), wxT("Failed to open document"));
		return false;
	}
}

bool QooleDoc::OnSaveDocument(const wxString& filename)
{
	// Check the extension.
	wxFileName pname = filename;
	wxString ext = pname.GetExt();
	if(ext == wxT("map"))
	{
		// Import the map file.
		return ExportMapFile(filename);
	}
	else if(ext == wxT("xql"))
	{
		return SaveXmlDocument(filename);
	}
	else if(ext == wxT("qle"))
	{
		return SaveClassicDocument(filename);
	}
	else
	{
		wxMessageBox(wxT("Unsupported file format " + ext), wxT("Failed to open document"));
		return false;
	}
}

bool QooleDoc::ExportDocument(const wxString &filename)
{
	// Check the extension.
	wxFileName pname = filename;
	wxString ext = pname.GetExt();
	if(ext == wxT("map"))
	{
		// Import the map file.
		return ExportMapFile(filename);
	}
	else if(ext == wxT("xql"))
	{
		return SaveXmlDocument(filename, true);
	}
	else if(ext == wxT("qle"))
	{
		return SaveClassicDocument(filename, true);
	}
	else
	{
		wxMessageBox(wxT("Unsupported file format " + ext), wxT("Failed to open document"));
		return false;
	}
}

bool QooleDoc::OnCloseDocument()
{
	if(!wxDocument::OnCloseDocument())
		return false;

	GetMainFrame()->GetTreeView()->RemoveDocument(this);
	if(GetMainFrame()->GetDeskTopDocument() == this)
		GetMainFrame()->SetDeskTopDocument(NULL);
	return true;
}

void QooleDoc::Modify(bool value)
{
	wxDocument::Modify(value);

	GetMainFrame()->UpdateFrameTitle();
}

void QooleDoc::LoadError(const wxString &error)
{
	wxMessageBox(error, wxT("Load Error"));
}

bool QooleDoc::LoadXmlDocument(const wxString &filename)
{
	wxXmlDocument xmlDoc;
	if(!xmlDoc.Load(filename))
		return false;

	// Check the root element.
	if(xmlDoc.GetRoot()->GetName() != wxT("qmap"))
	{
		LoadError(wxT("Invalid qoole document."));
		return false;
	}

	wxXmlNode *rootNode = xmlDoc.GetRoot();

	// Read the game name, entity set name and palette name.
	wxString gameName = rootNode->GetAttribute(wxT("game"), wxT(""));
	wxString entSetName = rootNode->GetAttribute(wxT("entitySet"), wxT(""));
	palName = rootNode->GetAttribute(wxT("palette"), wxT(""));

	// Check that the game name and the entity set name aren't empty.
	if(gameName.empty() || entSetName.empty())
	{
		LoadError(wxT("Qoole document hasn't game or entity set."));
		return false;
	}

	// Find the game.
	game = Game::Find(gameName);

	// Set the game.
	if(!Game::Set(game, palName, QDraw::textureGamma))
		return false;

	// Use the game grid and snap default size.
	gridSnapSize = game->GetDefaultGridSize();
	rotSnapSize = game->GetDefaultRotSnapSize();

	// Use by default grid and rotation snap.
	gridSnap = rotSnap = true;

	// Load the entity list.
	entList = EntList::Find(entSetName);
	EntList::Set(entList);

	// Get the root object node.
	wxXmlNode *objectNode = rootNode->GetChildren();
	while(objectNode && objectNode->GetName() != wxT("object"))
		objectNode = objectNode->GetNext();

	// Check if the root object node.
	if(!objectNode)
	{
		LoadError(wxT("Qoole document hasn't the root object."));
		return false;
	}

	// Load the root object.
	ASSERT(rootObject == NULL);
	rootObject = new Object;
	bool ret = rootObject->LoadObjFile(objectNode);

	if(ret)
	{
		// Notify the frames.
		SetFilename(filename, true);
		SetTitle(wxFileNameFromPath(filename));
		SetDocumentSaved(true);
		Modify(false);
		GetMainFrame()->GetTreeView()->AddDocument(this);
		GetMainFrame()->SetDeskTopDocument(this);

		UpdateAllViews();
	}
	else
	{
		delete rootObject;
		rootObject = NULL;
	}

	return ret;
}

bool QooleDoc::SaveXmlDocument(const wxString &filename, bool exporting)
{
	// Create the xml document.
	wxXmlDocument xmlDoc;
	xmlDoc.SetVersion(wxT("1.0"));

	// Create the root element.
	wxXmlNode *rootNode = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("qmap"));
	xmlDoc.SetRoot(rootNode);

	// Store the game name, entity set name and palette name.
	rootNode->AddAttribute(wxT("game"), game->GetName());
	rootNode->AddAttribute(wxT("entitySet"), entList->GetName());
	rootNode->AddAttribute(wxT("palette"), palName);

	// Save the root object.
	rootObject->SaveObjFile(rootNode);

	// Save the xml document.
	bool ret = xmlDoc.Save(filename);
	if(ret && !exporting)
	{
		Modify(false);
		SetFilename(filename, true);
		SetTitle(wxFileNameFromPath(filename));
		SetDocumentSaved(true);
	}
	return ret;
}

bool QooleDoc::LoadDocHeader(const wxString &pathName, wxString &gameName, wxString &entName, wxString &palName)
{
	wxString name;
	LFile inFile;

	if (!inFile.Open(pathName))
		return false;

	inFile.ResetLineNumber();

	if (strnicmp(inFile.GetNextLine(), "// QOOLE", 8) != 0)
		return false;

	while (strnicmp(inFile.GetNextLine(), "// Game:", 8) != 0)
	{
		if (inFile.GetLineNumber() > 10)
			return false;
	}

	name = wxString(inFile.GetLine() + 8, wxConvUTF8);
	name.Trim(false);
	name.Trim(true);
	gameName = name;

	while (strnicmp(inFile.GetNextLine(), "// EntList:", 11) != 0)
	{
		if (inFile.GetLineNumber() > 10)
			return false;
	}

	name = wxString(inFile.GetLine() + 11, wxConvUTF8);
	name.Trim(false);
	name.Trim(true);
	entName = name;

	while (strnicmp(inFile.GetNextLine(), "// Palette:", 11) != 0)
	{
		if (inFile.GetLineNumber() > 10)
		{
			palName = wxT("");
			return true;
		}
	}

	name = wxString(inFile.GetLine() + 11, wxConvUTF8);
	name.Trim(false);
	name.Trim(true);
	palName = name;

	return true;
}

bool QooleDoc::SaveDocHeader(const wxString &pathName, const wxString &gameName, const wxString &entName, const wxString &palName)
{
	FILE *out = fopen(pathName.utf8_str(), "w");
	if(!out)
		return false;

	fprintf(out, "// QOOLE 99 - http://qoole.gamedesign.net\n");
	fprintf(out, "// Game: %s\n", (const char *)gameName.utf8_str());
	fprintf(out, "// EntList: %s\n", (const char *)entName.utf8_str());
	fprintf(out, "// Palette: %s\n", (const char *)palName.utf8_str());

	fclose(out);
	return true;
}

bool QooleDoc::LoadClassicDocument(const wxString &filename)
{
	// Get the game type.
	wxString gameName, entName;
	palName = wxT("");
	game = NULL;
	entList = NULL;

	if(LoadDocHeader(filename, gameName, entName, palName))
	{
		game = Game::Find(gameName);
	}
	else
	{
		// Prompt the user for game/entity selection.
		QEntSel entSel(GetMainFrame());
		if(entSel.ShowModal() != wxID_OK)
			return false;

		if(entSel.gameName.empty())
			return false;

		gameName = entSel.gameName;
		entName = entSel.entName;

		game = Game::Find(gameName);
	}

	if(!Game::Set(game, palName, QDraw::textureGamma))
		return false;

	// Use the game grid and snap default size.
	gridSnapSize = game->GetDefaultGridSize();
	rotSnapSize = game->GetDefaultRotSnapSize();

	// Use by default grid and rotation snap.
	gridSnap = rotSnap = true;

	entList = EntList::Find(entName);
	EntList::Set(entList);

	wxString name = wxFileNameFromPath(filename);
	wxString ext = wxFileName(filename).GetExt();
	QDraw::OutputText(_("Loading map: %s...\n"), name.c_str());

	ProgressFunc prog(_("Loading Map"), wxString::Format(_("Loading %s"), name.c_str()));

	bool ret = false;
	bool import = false;
	wxString pathName = filename;
	if(ext.CmpNoCase(wxT("map")) == 0)
	{
		rootObject = MapIO::ReadMap(filename, ProgressFunc::SetPos);
		ret = rootObject != NULL;
		wxFileName newName = filename;
		newName.SetExt(wxT("xql"));
		pathName = newName.GetFullName();
		import = true;
	}
	else if(ext.CmpNoCase(wxT("qle")) == 0)
	{
		rootObject = new Object;
		ret = rootObject->LoadObjFile(filename, NULL, ProgressFunc::SetPos);
	}
	else
	{
		ASSERT(0); // Shouldn't reach here.
		return false;
	}

	if(ret)
	{
		// Notify the frames.
		SetFilename(pathName, true);
		SetTitle(wxFileNameFromPath(pathName));
		SetDocumentSaved(!import);
		Modify(import);
		GetMainFrame()->GetTreeView()->AddDocument(this);
		GetMainFrame()->SetDeskTopDocument(this);

		UpdateAllViews();
	}
	else
	{
		delete rootObject;
		rootObject = NULL;
	}

	return ret;
}

bool QooleDoc::SaveClassicDocument(const wxString &filename, bool exporting)
{
	// First save the header.
	if(!SaveDocHeader(filename, game->GetName(), entList->GetName(), palName))
		return false;

	ProgressFunc prog(_("Saving map"), _("Saving"));
	bool ret = rootObject->SaveObjFile(filename, true, prog.SetPos);

	if(ret && !exporting)
	{
		Modify(false);
		SetFilename(filename, true);
		SetTitle(wxFileNameFromPath(filename));
		SetDocumentSaved(true);
	}

	return ret;
}

bool QooleDoc::ImportMapFile(const wxString &filename)
{
	return LoadClassicDocument(filename);
}

bool QooleDoc::ExportMapFile(const wxString &filename)
{
	ASSERT(rootObject != NULL);

	/*if(game->IsExportWadSet())
		ExportWadSet(filename);

	if(game->IsSpecifyWadsSet())
		SpecifyWads();*/

	ProgressFunc prog(_("Exporting Map"),
			wxString::Format(_("Export %s."), filename.c_str()));

	bool ret = MapIO::WriteMap(filename, *rootObject, ProgressFunc::SetPos);

	return ret;
}

void QooleDoc::RegisterSpecialView(QooleView *view)
{
	specialViews.insert(view);
}

void QooleDoc::UnregisterSpecialView(QooleView *view)
{
	specialViews.erase(view);
}

void QooleDoc::UpdateAllViews(wxView *sender, wxObject *hint)
{
	SpecialViews::iterator it = specialViews.begin();
	for(; it != specialViews.end(); it++)
	{
		QooleView *view = *it;
		if(view)
			view->OnUpdate(sender, hint);
	}

	wxDocument::UpdateAllViews(sender, hint);
}

// Qoole stuffs.
void QooleDoc::ObjectsAdd(const LinkList<ObjectPtr> &newObjs,	Object &parent,
				bool updateViews, bool resetParent)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(newObjs.NumOfElm() > 0);
	for(int i = 0; i < newObjs.NumOfElm(); i++)
		ASSERT(newObjs[i].GetPtr()->GetParentPtr() == NULL);
	ASSERT(parent.IsMyAncestor(*rootObject) || &parent == rootObject);
#endif
	// Locking check.

	IterLinkList<ObjectPtr> iter(newObjs);
	iter.Reset();
	Object *pObj;
	while (!iter.IsDone()) {
		pObj = iter.GetNext()->GetPtr();
		parent.AddChild(*pObj, false);
	}

	if (resetParent)
		parent.SetBoundRadius();

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODSTRUCT;
		hint.scope = &parent;
		UpdateAllViews(NULL, &hint);
	}

}

void QooleDoc::ObjectsDelete(const LinkList<ObjectPtr> &delObjs, bool destroyObjs,
				   bool updateViews, bool resetParent)
{
	ASSERT(delObjs.NumOfElm() > 0);
	Object *pParent = delObjs[0].GetPtr()->GetParentPtr();

#ifndef NDEBUG 	// Sanity.
	ASSERT(pParent != NULL);
	ASSERT(pParent == rootObject|| pParent->IsMyAncestor(*rootObject));
	for(int i = 1; i < delObjs.NumOfElm(); i++)
		ASSERT(delObjs[i].GetPtr()->GetParentPtr() == pParent);
#endif
	// Locking check.

	IterLinkList<ObjectPtr> iter(delObjs);
	iter.Reset();
	Object *pObj;
	while (!iter.IsDone())
	{
		pObj = iter.GetNext()->GetPtr();
		pParent->RemoveChild(*pObj, false);
		if (destroyObjs)
			delete pObj;
	}

	if (resetParent)
		pParent->SetBoundRadius();

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODSTRUCT;
		hint.scope = pParent;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::ObjectReplace(Object &replacee, Object &replacer,
				   bool updateViews, bool resetParent)
{
	// Sanity.
	ASSERT(replacer.GetParentPtr() == NULL);

	// Locking Check.

	Object *pParent = replacee.GetParentPtr();
	ASSERT(pParent != NULL);

	int index;

	pParent->RemoveChild(replacee, false, &index);
	pParent->AddChild(replacer, resetParent, index);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODSTRUCT | DUAV_OBJSSEL;
		hint.scope = pParent;
		UpdateAllViews(NULL, &hint);
	}
}


// Move the object node in the map tree.
void QooleDoc::MoveObjNodesInTree(const LinkList<ObjectPtr> &moveObjs,
						Object *pNewParent, bool updateViews)
{
	ASSERT(NULL);
}


// Shifts center of object.  Counter adjust all components.
// void TranslateObjectCenter(Object &obj, const Vector3d &deltaVec);

void QooleDoc::ObjectsMove(const LinkList<ObjectPtr> &mObjs,
				 const TransSpace &operateSpace,
				 const Vector3d &mVec, bool updateViews)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(mObjs.NumOfElm() > 0);
	for(int i = 0; i < mObjs.NumOfElm(); i++)
		ASSERT(mObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif

	Matrix44 m1;
	Vector3d posVec, dVec;

	operateSpace.CalTransSpaceMatrix(m1.SetIdentity());
	m1.Transform(dVec, mVec);
	dVec.SubVector(operateSpace.GetPosition());

	IterLinkList<ObjectPtr> iter(mObjs);
	iter.Reset();
	Object *pObj;
	while (!iter.IsDone()) {
		pObj = iter.GetNext()->GetPtr();
		(pObj->GetPosition(posVec)).AddVector(dVec);
		pObj->TransSpace::SetPosition(posVec);
	}

	Object *pParent = mObjs[0].GetPtr()->GetParentPtr();
	if (pParent)
		pParent->SetBoundRadius();

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB;
		hint.scope = pParent;
		UpdateAllViews(NULL, &hint);
	}
}


void QooleDoc::ObjectsRotate(const LinkList<ObjectPtr> &rObjs,
				   const TransSpace &operateSpace,
				   const Vector3d &rotPt, float rotAngle,
				   bool updateViews)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(rObjs.NumOfElm() > 0);
	for(int i = 0; i < rObjs.NumOfElm(); i++)
		ASSERT(rObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif
	// Locking checks.

	Matrix44 m1, m2, m3;

	// Translate coord into view's system.
	operateSpace.CalInvTransSpaceMatrix(m1.SetIdentity());

	// Translate into rotPt's pos coord system.
	m2.SetInvTranslation(rotPt);
	m3.Multiply(m2, m1);

	// Rotate rotAng.
	m2.SetRotateY(rotAngle);
	m1.Multiply(m2, m3);

	// Translate back into edit scope pos coord system.
	m2.SetTranslation(rotPt);
	m3.Multiply(m2, m1);

	// Translate back into edit scope orientation system.
	operateSpace.CalTransSpaceMatrix(m2.SetIdentity());
	m1.Multiply(m2, m3);

	// Iter through the objects and apply transformation.
	Object *pObj;
	Vector3d fVec, tVec, pVec;
	IterLinkList<ObjectPtr> iter(rObjs);
	iter.Reset();
	while (!iter.IsDone()) {
		pObj = iter.GetNext()->GetPtr();

		pObj->GetPosition(pVec);
		pObj->GetOrientation(fVec, tVec);
		fVec.AddVector(pVec);
		tVec.AddVector(pVec);

		m1.Transform(pVec);
		(m1.Transform(fVec)).SubVector(pVec);
		(m1.Transform(tVec)).SubVector(pVec);

		pObj->TransSpace::SetPosition(pVec);
		pObj->TransSpace::SetOrientation(fVec, tVec);
	}

	// Reset the bounding information for the scope object.
	Object *pParent = rObjs[0].GetPtr()->GetParentPtr();
	if (pParent)
		pParent->SetBoundRadius();

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB;
		hint.scope = pParent;
		UpdateAllViews(NULL, &hint);
	}
}


void QooleDoc::GetObjectsScaleBoundBox(const LinkList<ObjectPtr> &sObjs,
							 const TransSpace &operateSpace,
							 Vector3d &minBoundVec,
							 Vector3d &maxBoundVec) const
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(sObjs.NumOfElm() > 0);
	for(int i = 0; i < sObjs.NumOfElm(); i++)
		ASSERT(sObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif

	Matrix44 m1;
	operateSpace.CalInvTransSpaceMatrix(m1.SetIdentity());

	// Copy and move all selected objects into temp obj.
	Object *pOpObj = new Object;
	Object *pSelObj;
	IterLinkList<ObjectPtr> iter(sObjs);
	iter.Reset();
	while (!(iter.IsDone())) {
		pSelObj = (iter.GetNext())->GetPtr();
		pSelObj = new Object(*pSelObj);
		pSelObj->Transform(m1);
		pOpObj->AddChild(*pSelObj, false);
	}
	pOpObj->SetBoundRadius();
	pOpObj->GetBoundingVectors(minBoundVec, maxBoundVec);
	delete pOpObj;
}

Object &QooleDoc::ObjectsScaleBegin(const LinkList<ObjectPtr> &sObjs,
						  const TransSpace &operateSpace,
						  const Vector3d &basisVec)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(sObjs.NumOfElm() > 0);
	for(int i = 0; i < sObjs.NumOfElm(); i++)
		ASSERT(sObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif

	// Remember scope.
	Object *pScope = sObjs[0].GetPtr()->GetParentPtr();

	// Create a temp holder obj and set it's pos and orientation.
	Object *pOpObj = new Object;

	Matrix44 m1;
	operateSpace.CalTransSpaceMatrix(m1.SetIdentity());

	Vector3d pVec;
	m1.Transform(pVec, basisVec);

	pOpObj->SetPosition(pVec);
	pOpObj->SetOrientation(operateSpace.GetOrientation());

	// Find the transformation matrix for selected objs.
	Matrix44 m2;

	// Transform into view space's system.
	operateSpace.CalInvTransSpaceMatrix(m1.SetIdentity());

	// Translate into basisVec's system.
	m2.SetInvTranslation(basisVec);
	m2.Multiply(m1);

	// Move all the selected objects into the temp obj.
	Object *pSelObj;
	IterLinkList<ObjectPtr> iter(sObjs);
	iter.Reset();
	while (!(iter.IsDone())) {
		pSelObj = (iter.GetNext())->GetPtr();
		pSelObj->Transform(m2);
		pSelObj->GetParent().RemoveChild(*pSelObj, false);
		pOpObj->AddChild(*pSelObj, false);
	}
	pOpObj->SetBoundRadius();
	pScope->AddChild(*pOpObj);

	return *pOpObj;
}

void QooleDoc::ObjectsScaleChange(const LinkList<ObjectPtr> &sObjs,
						const Vector3d &newScaleVec,
						bool updateViews)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(sObjs.NumOfElm() > 0);
	for(int i = 0; i < sObjs.NumOfElm(); i++)
		ASSERT(sObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif

	Object *pOpObj = (sObjs[0].GetPtr())->GetParentPtr();
	ASSERT(pOpObj != NULL);

	pOpObj->SetScale(newScaleVec);

	Object *pScope = pOpObj->GetParentPtr();
	ASSERT(pScope != NULL);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB | DUAV_NOQTREEVIEW;
		hint.scope = pScope;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::ObjectsScaleEnd(const LinkList<ObjectPtr> &sObjs,
					 bool updateViews)
{
#ifndef NDEBUG 	// Sanity.
	ASSERT(sObjs.NumOfElm() > 0);
	for(int i = 0; i < sObjs.NumOfElm(); i++)
		ASSERT(sObjs[i].GetPtr()->IsMyAncestor(*rootObject));
#endif

	Object *pOpObj = (sObjs[0].GetPtr())->GetParentPtr();
	ASSERT(pOpObj != NULL);
	Object *pScope = pOpObj->GetParentPtr();
	ASSERT(pScope != NULL);

	// Float the selected objs back to their original scope.
	pOpObj->FloatChildren();

	// Delete the temp obj;
	delete &(pScope->RemoveChild(*pOpObj));

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB;
		hint.scope = pScope;
		UpdateAllViews(NULL, &hint);
	}
}


// Brush Manipulation.
void QooleDoc::BrushManipulateBegin(Object &obj)
{
	// Sanity.
	ASSERT(obj.IsMyAncestor(*rootObject));
	ASSERT(obj.HasBrush());
	// Can't do integrity check, because it rebuilds plane & edge info.
	// ASSERT((obj.GetBrush()).IntegrityCheck());
	ASSERT(holdBrush == NULL);
	ASSERT(holdManipObj == NULL);

	holdBrush = new Geometry(obj.GetBrush());
	holdManipObj = &obj;
	brushManipInit = true;
}

Geometry *QooleDoc::BrushManipulateEnd(bool updateViews)
{
	// Sanity.
	ASSERT(holdBrush != NULL);
	ASSERT(holdManipObj != NULL);
	ASSERT(holdManipObj->HasBrush());

	bool ok;
	Geometry *rtnVal;
	Geometry *brush = &holdManipObj->GetBrush();

	ok = brush->IntegrityCheck();
	if (ok)
	{
		rtnVal = holdBrush;
	}
	else
	{
		rtnVal = holdManipObj->SetBrush(holdBrush);
		delete rtnVal;
		rtnVal = NULL;
	}

	holdManipObj->SetBoundRadius(true);

	holdManipObj = NULL;
	holdBrush = NULL;
	brushManipInit = false;

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB;
		hint.scope = holdManipObj;
		UpdateAllViews(NULL, &hint);
	}

	return rtnVal;
}


void QooleDoc::FaceMove(const GPolygon *facePtr, const Vector3d &deltaVec,
			  const TransSpace &operateSpace, bool updateViews)

{
	// Sanity.
	ASSERT(holdManipObj != NULL);
	ASSERT(facePtr != NULL);

	Geometry *brush = &(holdManipObj->GetBrush());
	Vector3d dVec(deltaVec), oVec;
	Matrix44 m1;

	if (brushManipInit)
	{
		brush->BreakPlanes(*facePtr);
		brushManipInit = false;
	}

	// Translate deltaVec into object's space.
	holdManipObj->CalInvTransSpaceMatrix(m1.SetIdentity());
	operateSpace.CalTransSpaceMatrix(m1);
	m1.Transform(dVec);
	m1.Transform(oVec, Vector3d::origVec);
	dVec.SubVector(oVec);

	// Then move the face.
	brush->MoveFace(*facePtr, dVec);
	holdManipObj->SetBoundRadius(true);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB | DUAV_NOQTREEVIEW;
		hint.scope = holdManipObj->GetParentPtr();
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::EdgeMove(const Edge3d *edgePtr, const Vector3d &deltaVec,
			  const TransSpace &operateSpace, bool updateViews)

{
	// Sanity.
	ASSERT(holdManipObj != NULL);
	ASSERT(edgePtr != NULL);

	Geometry *brush = &(holdManipObj->GetBrush());
	Vector3d dVec(deltaVec), oVec;
	Matrix44 m1;

	if (brushManipInit) {
		brush->BreakPlanes(*edgePtr);
		brushManipInit = false;
	}

	// Translate deltaVec into object's space.
	holdManipObj->CalInvTransSpaceMatrix(m1.SetIdentity());
	operateSpace.CalTransSpaceMatrix(m1);
	m1.Transform(dVec);
	m1.Transform(oVec, Vector3d::origVec);
	dVec.SubVector(oVec);

	// Then move the edge.
	brush->MoveEdge(*edgePtr, dVec);
	holdManipObj->SetBoundRadius(true);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB | DUAV_NOQTREEVIEW;
		hint.scope = holdManipObj->GetParentPtr();
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::VertexMove(int vIndex, const Vector3d &deltaVec,
				const TransSpace &operateSpace, bool updateViews)

{
	// Sanity.
	ASSERT(holdManipObj != NULL);
	ASSERT(vIndex >= 0 && vIndex < holdManipObj->GetBrush().GetNumVertices());

	Geometry *brush = &(holdManipObj->GetBrush());
	Vector3d dVec(deltaVec), oVec;
	Matrix44 m1;

	if (brushManipInit)
	{
		brush->BreakPlanes(vIndex);
		brushManipInit = false;
	}

	// Translate deltaVec into object's space.
	holdManipObj->CalInvTransSpaceMatrix(m1.SetIdentity());
	operateSpace.CalTransSpaceMatrix(m1);
	m1.Transform(dVec);
	m1.Transform(oVec, Vector3d::origVec);
	dVec.SubVector(oVec);

	// Then move the vertex.
	brush->MoveVertex(vIndex, dVec);
	holdManipObj->SetBoundRadius(true);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB | DUAV_NOQTREEVIEW;
		hint.scope = holdManipObj->GetParentPtr();
		UpdateAllViews(NULL, &hint);
	}
}

// Replacing brush in object.
Geometry *QooleDoc::BrushReplace(Object &brushObj, Geometry &newBrush,
					   bool updateViews)
{
	Geometry *rtnVal;
	rtnVal = brushObj.SetBrush(&newBrush);
	brushObj.SetBoundRadius(true);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODATTRIB;
		hint.scope = holdManipObj->GetParentPtr();
		UpdateAllViews(NULL, &hint);
	}

	return rtnVal;
}


// Hollow Brush.
bool QooleDoc::HollowBrush(Object &brushObj, bool inward, float thickness,
				 bool updateViews)

{
	// Sanity.
	ASSERT(brushObj.IsMyAncestor(*rootObject));
	ASSERT(brushObj.HasBrush());

	bool modified;
	if (inward)
		modified = brushObj.HollowInBrush(thickness);
	else
		modified = brushObj.HollowOutBrush(thickness);

	if (modified)
	{
		Modify(true);
		if (updateViews)
		{
			QDocHint hint;
			hint.flags = DUAV_OBJSSEL | DUAV_OBJSMODSTRUCT;
			hint.scope = brushObj.GetParentPtr();
			UpdateAllViews(NULL, &hint);
		}
	}

	return modified;
}


// CSG Subtraction...
bool QooleDoc::CSGSubtract(Object &cuttee, const LinkList<ObjectPtr> &cutters,
				 bool updateViews)

{
	ASSERT(NULL);
}

// CSG Intersection...
Object *QooleDoc::CSGIntersect(Object &scope, const LinkList<ObjectPtr> &nodes,
					 bool updateViews)

{
	ASSERT(NULL);
	return NULL;
}


// Plane clipping.
void QooleDoc::PlaneClipObjects(Plane &cutingPlane, const LinkList<ObjectPtr> &sObjs,
					  bool updateViews)

{
	ASSERT(NULL);
}

// Flips an object across the given plane.
void QooleDoc::FlipObjects(const LinkList<ObjectPtr> &sObjs, const Plane &flipPlane,
				 bool updateViews)

{
	ASSERT(NULL);
}


// Grouping
void QooleDoc::GroupObjects(Object &newGroup, const LinkList<ObjectPtr> &objPtrs,
				  bool updateViews)

{
	// PreCondition.
	ASSERT(newGroup.GetParentPtr() == NULL);
	ASSERT(newGroup.GetNumChildren() == 0);
	ASSERT(!newGroup.HasBrush() && !newGroup.IsItemNode());

	// Find GrandParent.
	Object *pGrandParent = (objPtrs[0].GetPtr())->GetParentPtr();
	ASSERT(pGrandParent != NULL);

	// Move the children nodes into new group (parent) node.
	Vector3d avePosVec(0.0f, 0.0f, 0.0f);
	Object *pObj;
	IterLinkList<ObjectPtr> iter(objPtrs);
	iter.Reset();
	while (!iter.IsDone())
	{
		pObj = (iter.GetNext())->GetPtr();
		ASSERT(pObj->GetParentPtr() == pGrandParent);
		pGrandParent->RemoveChild(*pObj, false);
		newGroup.AddChild(*pObj, false);
		avePosVec.AddVector(pObj->GetPosition());
	}
	avePosVec.MultVector(1.0f / objPtrs.NumOfElm());

	// Adjust children's positions.
	Vector3d pVec;
	iter.Reset();
	while (!iter.IsDone())
	{
		pObj = (iter.GetNext())->GetPtr();
		(pObj->GetPosition(pVec)).SubVector(avePosVec);
		pObj->TransSpace::SetPosition(pVec);
	}

	// Reset the new group's info.
	Vector3d sVec(1.0f, 1.0f, 1.0f);
	SphrVector oriVec(0.0f, 0.0f, 0.0f);
	newGroup.TransSpace::SetPosition(avePosVec);
	newGroup.TransSpace::SetOrientation(oriVec);
	newGroup.TransSpace::SetScale(sVec);
	newGroup.SetBoundRadius();

	// Add the new group back in.
	pGrandParent->AddChild(newGroup);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODSTRUCT | DUAV_OBJSMODATTRIB;
		hint.scope = pGrandParent;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::UngroupObjects(Object &disbandGroup, bool updateViews)

{
	// PreCondition.
	ASSERT(disbandGroup.GetNumChildren() > 0);
	ASSERT(!disbandGroup.IsRoot());

	// Get grand parent.
	Object *pGrandParent = disbandGroup.GetParentPtr();

	// Just float the children up.
	// Already implemented in Object.cpp.
	disbandGroup.FloatChildren(false);

	// Don't delete the DisbandGruop object.
	// Leave it for the callee to deal with.
	pGrandParent->RemoveChild(disbandGroup, true);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_OBJSMODSTRUCT | DUAV_OBJSMODATTRIB;
		hint.scope = pGrandParent;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::SetChildFrame(wxPanel *childFrame)
{
	this->childFrame = childFrame;
}

void QooleDoc::SetTitle(const wxString &title)
{
	wxDocument::SetTitle(title);

	if(childFrame && childFrame->IsKindOf(CLASSINFO(QChildFrame)))
	{
		QChildFrame *frame = static_cast<QChildFrame*> (childFrame);
		frame->SetTitle(title);
	}

	// TODO: Update the tree view.
}

// Texture stuff.
Texture *QooleDoc::applyTexture = NULL;
bool QooleDoc::TextureApplyObj(Object &obj)

{
	if(!obj.HasBrush())
		return true;

	Geometry *pBrush = &(obj.GetBrush());
	for(int i = 0; i < pBrush->GetNumFaces(); i++)
		pBrush->GetFaceTexturePtr(i)->SetTexture(applyTexture);

	return true;
}

void QooleDoc::TextureApplyObjs(LinkList<ObjectPtr> &objs, const wxString &name,
					  bool updateViews)

{
	applyTexture = game->GetTexDB()->FindTexture(name);

	IterLinkList<ObjectPtr> iter(objs);
	iter.Reset();
	while(!iter.IsDone()) {
		Object *pObj = iter.GetNext()->GetPtr();
		pObj->PreOrderApply(QooleDoc::TextureApplyObj);
	}

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_NOQTREEVIEW | DUAV_OBJTEXMODATTRIB;
		hint.scope = NULL;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::TextureApplyFace(Object &texObj, int faceIndex, const wxString &name,
					  bool updateViews)

{
	ASSERT(texObj.HasBrush());
	Geometry *brush = &texObj.GetBrush();

	ASSERT(faceIndex >= 0 && faceIndex < brush->GetNumFaces());

	FaceTex *faceTex = brush->GetFaceTexturePtr(faceIndex);
	faceTex->SetTName(name);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_NOQTREEVIEW | DUAV_OBJTEXMODATTRIB;
		hint.scope = &texObj;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::TextureManipulate(Object &texObj, int faceIndex, int xOff, int yOff,
					   float rotAng, float xScale, float yScale, bool texLock,
					   bool updateViews)

{
	ASSERT(texObj.HasBrush());
	Geometry *pBrush = &(texObj.GetBrush());

	ASSERT(faceIndex >= 0 && faceIndex < pBrush->GetNumFaces());

	FaceTex *pFaceTex = pBrush->GetFaceTexturePtr(faceIndex);
	pFaceTex->SetTInfo(xOff, yOff, rotAng, xScale, yScale);
	pFaceTex->SetTexLock(texLock);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = DUAV_NOQTREEVIEW | DUAV_OBJTEXMODATTRIB;
		hint.scope = &texObj;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::ModifyTexSurfAttrib(Object &texObj, int faceIndex, unsigned int newVal,
						 bool updateViews)

{
	ASSERT(NULL);
}

void QooleDoc::ModifyTexValAttrib(Object &texObj, int faceIndex, unsigned int newVal,
						bool updateViews)

{
	ASSERT(NULL);
}

void QooleDoc::ModifyContentAttrib(Object &opObj, unsigned int newVal,
						 bool updateViews)

{
	ASSERT(NULL);
}


// Entity handling
void QooleDoc::EntitySetKey(Entity *ent, const wxString &key,  const wxString &arg,
				  bool updateViews)

{
	ASSERT(ent != NULL);
	ent->SetKey(key, arg);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = /*DUAV_NOQTREEVIEW*/
				DUAV_QPROPPAGES | DUAV_OBJENTMODATTRIB;
		UpdateAllViews(NULL, &hint);
	}
}

void QooleDoc::EntityApply(Object *obj, Entity *ent, bool updateViews)

{
	ASSERT(obj != NULL);

	// NOTE: old entity isn't deleted! (OpEntityApply handles this)
	obj->SetEntity(ent);

	Modify(true);
	if (updateViews)
	{
		QDocHint hint;
		hint.flags = /*DUAV_NOQTREEVIEW*/
				   DUAV_NOQVIEWS | DUAV_OBJENTMODATTRIB;
		UpdateAllViews(NULL, &hint);
	}
}

Selector *QooleDoc::GetSelector()
{
	return selector;
}

void QooleDoc::SetSelector(Selector *sel)
{
	selector = sel;
}

bool QooleDoc::GetGridSnap() const
{
	return gridSnap;
}

void QooleDoc::SetGridSnap(bool snap)
{
	gridSnap = snap;
}

int QooleDoc::GetGridSnapSize() const
{
	return gridSnapSize;
}

void QooleDoc::SetGridSnapSize(int size)
{
	gridSnapSize = size;
}

bool QooleDoc::GetRotSnap() const
{
	return rotSnap;
}

void QooleDoc::SetRotSnap(bool snap)
{
	rotSnap = snap;
}

int QooleDoc::GetRotSnapSize() const
{
	return rotSnapSize;
}

void QooleDoc::SetRotSnapSize(int size)
{
	rotSnapSize = size;
}
